package djinni

import java.io.File

import djinni.ast._
import djinni.generatorTools._

class NodeJsHelperFilesGenerator(spec: Spec, helperFileDescriptor: NodeJsHelperFilesDescriptor) extends Generator(spec) {

  def generateObjectWrapperHeader(): Unit = {
    val define = ("DJINNI_GENERATED_" + helperFileDescriptor.ObjectWrapperName + "_" + spec.cppHeaderExt).toUpperCase
    createFile(spec.nodeOutFolder.get, helperFileDescriptor.ObjectWrapperHeader, { (w: writer.IndentWriter) =>

      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni")

      w.wl(s"#ifndef $define")
      w.wl(s"#define $define")
      w.wl
      w.wl("#include <memory>")
      w.wl
      w.wl("#include <nan.h>")
      w.wl("#include <node.h>")
      w.wl
      w.wl("namespace djinni").bracedEndNested(" // djinni", false) {
        w.wl("namespace js").bracedEndNested(" //js", false) {
          w.wl
          w.wl("template<typename T>")
          w.wl("class ObjectWrapper: public Nan::ObjectWrap").bracedEnd(";") {
            w.wlOutdent("public:")
            w.wl("static void Wrap(const std::shared_ptr<T>& realObjectPtr, v8::Local<v8::Object> newHandleExposedToJS)").braced {
              w.wl("auto wrapper = new ObjectWrapper(realObjectPtr);")
              w.wl("wrapper->Nan::ObjectWrap::Wrap(newHandleExposedToJS);")
            }
            w.wl("static std::shared_ptr<T> Unwrap(v8::Local<v8::Object> handle)").braced {
              w.wl("return Nan::ObjectWrap::Unwrap<ObjectWrapper<T>>(handle)->_ptr;")
            }
            w.wlOutdent("private:")
            w.wl("explicit ObjectWrapper(const std::shared_ptr<T>& realObjectPtr)	: _ptr(realObjectPtr) {};")
            w.wl("std::shared_ptr<T> _ptr;")
          }
        }
      }
      w.wl(s"#endif //$define")
    })
  }

  def generateHexUtilsHeader(): Unit = {
    val define = ("DJINNI_GENERATED_" + helperFileDescriptor.HexUtilsName + "_" + spec.cppHeaderExt).toUpperCase
    createFile(spec.nodeOutFolder.get, helperFileDescriptor.HexUtilsHeader, { (w: writer.IndentWriter) =>

      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni")

      w.wl(s"#ifndef $define")
      w.wl(s"#define $define")
      w.wl
      w.wl("#include <memory>")
      w.wl
      w.wl("#include <string>")
      w.wl("#include <vector>")
      w.wl
      w.wl("namespace djinni").bracedEndNested(" // djinni", false) {
        w.wl("namespace js").bracedEndNested(" //js", false) {
          w.wl
          w.wl("namespace hex").bracedEnd(";") {
            w.wl("std::vector<uint8_t> toByteArray(const std::string &str);")
            w.wl("std::string toString(const std::vector<uint8_t>& data);")
            w.wl("uint8_t digitToByte(char c);")
            w.wl("std::string toString(const std::vector<uint8_t>& data, bool uppercase);")
            w.wl("char byteToDigit(uint8_t byte, bool uppercase);")
          }
        }
      }
      w.wl(s"#endif //$define")
    })
  }

  def generateHexUtilsCpp(): Unit = {
    createFile(spec.nodeOutFolder.get, helperFileDescriptor.HexUtilsCpp, { (w: writer.IndentWriter) =>

      w.wl("// AUTOGENERATED FILE - DO NOT MODIFY!")
      w.wl("// This file generated by Djinni")
      w.wl("#include <stdexcept>")
      w.wl(s"""#include "${helperFileDescriptor.HexUtilsHeader}" """)
      w.wl("namespace djinni").bracedEndNested(" // djinni", false) {
        w.wl("namespace js").bracedEndNested(" //js", false) {
          w.wl
          w.wl("std::vector<uint8_t> hex::toByteArray(const std::string &str)").braced {
            w.wl("std::vector<uint8_t> bytes(str.length() / 2 + str.length() % 2);")
            w.wl("auto offset = str.length() % 2 != 0 ? 1 : 0;")
            w.wl("uint8_t byte = 0;")
            w.wl("for (auto index = 0; index < bytes.size(); index++)").braced {
              w.wl("if (index == 0 && str.length() % 2 != 0)").braced {
                w.wl("byte = hex::digitToByte(str[0]);")
              }
              w.wl("else").braced {
                w.wl("byte = hex::digitToByte(str[index * 2 - offset]) << 4;")
                w.wl("byte = byte + (hex::digitToByte(str[index * 2 + 1 - offset]));")
              }
              w.wl("bytes[index] = byte;")
            }
            w.wl("return bytes;")
          }

          w.wl("uint8_t hex::digitToByte(char c)").braced {
            w.wl("if (c >= '0' && c <= '9')").braced {
              w.wl("return (uint8_t)(c - '0');")
            }
            w.wl("else if (c >= 'a' && c <= 'f')").braced {
              w.wl("return (uint8_t)(0x0A + c - 'a');")
            }
            w.wl("else if (c >= 'A' && c <= 'F')").braced {
              w.wl("return (uint8_t)(0x0A + c - 'A');")
            }
            w.wl("throw std::invalid_argument(\"Invalid hex character\");")
          }

          w.wl("std::string hex::toString(const std::vector<uint8_t>& data, bool uppercase)").braced {
            w.wl("std::string str(data.size() * 2, '0');")
            w.wl("for (auto index = 0; index < data.size(); index++)").braced {
              w.wl("str[index * 2] = byteToDigit(data[index] >> 4, uppercase);")
              w.wl("str[index * 2 + 1] = byteToDigit((uint8_t) (data[index] & 0xF), uppercase);")
            }
            w.wl("return str;")
          }

          w.wl("std::string hex::toString(const std::vector<uint8_t>& data)").braced {
            w.wl("return toString(data, false);")
          }

          w.wl("char hex::byteToDigit(uint8_t byte, bool uppercase)").braced {
            w.wl("byte = (uint8_t) (0xF < byte ? 0xF : byte);")
            w.wl("if (byte < 0xA)").braced {
              w.wl("return '0' + byte;")
            }
            w.wl("else if (uppercase)").braced {
              w.wl("return (char)('A' + (byte - 0xA));")
            }
            w.wl("else").braced {
              w.wl("return (char)('a' + (byte - 0xA));")
            }
          }
        }
      }
    })
  }

  override def generate(idl: Seq[TypeDecl]): Unit = {
    generateObjectWrapperHeader()
    generateHexUtilsHeader()
    generateHexUtilsCpp()
  }

  override def generateEnum(origin: String, ident: Ident, doc: Doc, e: Enum): Unit = ???

  override def generateRecord(origin: String, ident: Ident, doc: Doc, params: Seq[TypeParam], r: Record): Unit = ???

  override def generateInterface(origin: String, ident: Ident, doc: Doc, typeParams: Seq[TypeParam], i: Interface): Unit = ???
}


